#!/usr/bin/env python
#
# Copyright 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

""" The XMPP chat-bot handler for FrackIM """
# Override use of default Django version by explicitly setting version to use.
# See: http://code.google.com/appengine/docs/python/tools/libraries.html#Django
from google.appengine.dist import use_library
use_library('django', '1.2')

import logging
import sys
sys.path.insert(0, "..")
import time

from common.dbclasses import MessageToMatch

from google.appengine.api import prospective_search as matcher
from google.appengine.api.app_identity import get_application_id

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import xmpp_handlers

def RemoveResourceIdentifier(jid):
  # Remove resourcepart from JID to get a base JID
  return jid.split('/')[0]

class XmppHandler(xmpp_handlers.CommandHandler):
  """Handler class for XMPP Chat-bot interactions."""

  def s_command(self, message=None):
    """Implements: /s, synonym for /subscribe."""
    self.subscribe_command(message)

  def subscribe_command(self, message=None):
    """Implements: /subscribe <query>. Subscriptions are logged to database."""
    subscriber = RemoveResourceIdentifier(message.sender)
    query = message.arg
    if query:
      sub_id = '%s/%d' % (subscriber, int(time.time() * 10000))
      try:
        matcher.subscribe(MessageToMatch, query, sub_id)
      except:
        message.reply('Something went wrong.')
      else:
        message.reply('Subscription accepted...')
    else:
      message.reply('/subscribe <query>')

  def u_command(self, message=None):
    """Implements: /u, synonym for /unsubscribe."""
    self.unsubscribe_command(message)

  def unsubscribe_command(self, message=None):
    """Implements: /unsubscribe <subscription number>."""
    sub_num = 0
    try:
      sub_num = int(message.arg)
    except:
      sub_num = 0
    if not sub_num:
      message.reply('You must specify a subscription number.')
      return

    subscriber = RemoveResourceIdentifier(message.sender)
    sub_list = matcher.list_subscriptions(MessageToMatch, subscriber)
    if sub_num > len(sub_list):
      message.reply('Invalid subscription number')
      return
    (sub_id, query, expiration_time, state, error) = sub_list[sub_num -1]
    if sub_id.find(subscriber) != 0:
      message.reply('Invalid subscription number.')
      return
    try:
      matcher.unsubscribe(MessageToMatch, sub_id)
    except:
      error_text = 'Sorry... %s, %s, %s' % (sys.exc_info())
      logging.error(error_text)
      message.reply(error_text)
    else:
      message.reply('Unsubscribed: %s' % query)

  def l_command(self, message=None):
    """Implements: /l, synonym for /list."""
    self.list_command(message)

  def list_command(self, message=None):
    """Implements: /list."""
    subscriber = RemoveResourceIdentifier(message.sender)

    sub_list = matcher.list_subscriptions(MessageToMatch, subscriber)
    count = 0
    response = ''
    for (sub_id, query, expiration_time, state, error) in sub_list:
      count += 1
      warning = ''
      if sub_id.find(subscriber) == 0:
        if state != matcher.SubscriptionState.OK:
          warning = '!! %s !! ' % (matcher.subscription_state_name(state))
        response += '[%d] %s%s\n' % (count, warning, query)
      else:
        #We have exhausted any and all sub_ids for this user
        break
    if response:
      response = '[sub_id] <query>\n' + response
      message.reply(response)
    else:
      message.reply('You have no subscriptions.')

  def h_command(self, message=None):
    """Implements: /h, synonym for /help."""
    self.help_command(message)

  def help_command(self, message=None):
    """Implements: /help."""
    message.reply(HELP_TEXT)

  def text_message(self, message=None):
    """Handles all non-command messages."""
    doc = MessageToMatch()
    doc.size = len(message.body)
    if doc.size:
      doc.follow = RemoveResourceIdentifier(message.sender)
      doc.track = message.body
      doc.snippet = 'via XMPP: %s said: %s' % (doc.follow, doc.track)
      try:
        matcher.match(doc, result_batch_size=10)
      except:
        error_text = 'Sorry... %s, %s, %s' % (sys.exc_info())
        logging.info(error_text)
        message.reply(error_text)
      else:
        logging.info("Text Message: " + doc.snippet)
        # Remove comment to store messages in database
        #doc.put()
        pass

  def unhandled_command(self, message=None):
    """All unrecognized commands will trigger help."""
    message.reply('Unhandled command...\n %s' % (HELP_TEXT))

def main(argv):
  application = webapp.WSGIApplication(
      [('/_ah/xmpp/message/chat/', XmppHandler),
      ], debug=True)
  util.run_wsgi_app(application)

HELP_TEXT = """
Commands include:
/subscribe [follow:<jid or feed_url>] [track:<word or "phrase">]
/unsubscribe <index number>
/list
/help
Short Form cmds: "/s, /u, /l, /h"

Any text other than a command will be published to all subscribed listeners who
have matching subscriptions.

Example Subscriptions:
  /subscribe track:rose
  /subscribe follow:bob@example.com
  /subscribe follow:bob AND track:"A Phrase"
AND, OR, and NOT (all caps) are supported as well as parentheses for grouping.
The Default Boolean operator is AND.
"""

if __name__ == '__main__':
  main(sys.argv)

